defmodule PsqlDb do
	@moduledoc """
	    Postgresql 数据库函数
	    ! 结尾的: 要抛出异常
	    非! 结尾的: 不抛出异常
	    query/1: 执行sql语句, 返回 rows
	    query!/1: 执行sql语句,检查受影响的行数大于0,返回 rows, 否则抛出异常
	"""

	require Logger

	@tran_conn :psql_conn
	@insert :insert
	@delete :delete
	@update :update
	@psql_pool :psql_pool


	def children() do
		pid = :erlang.whereis(@psql_pool)
		args = AppConfigure.fetch(:postgres)
		IO.inspect({__MODULE__, __ENV__.line, "pgsql_config", args})
		if pid == :undefined || pid == nil || (is_pid(pid) && !Process.alive?(pid)) do
			[{Postgrex, args ++ [name: @psql_pool]}]
		else
			[]
		end
	end

	def saga_transaction(sage, args, opts \\ []) do
		saga_transaction(sage, get_conn(), args, opts)
	end

	@doc """
	%% -----------------------------------------------------------------
	%% Func:
	%% Description: saga 事务,
		run的第一个函数的第一个参数是前几步执行的结果的返回.example: %{orders: any, notice: any, test: any}
		第二个参数是传进去的参数
		补偿函数:
			第一个暂时不用, 第二个是前几步的执行结果, 第三个是传进去的参数
	%% Args: sage: %Sage{} 结构体, conn: postgrex 注册名称 | postgrex 进程; args: 参数
	%% Returns:
	%% -----------------------------------------------------------------
	sage:
	    run(name, 写数据库, 写数据库失败补偿(不能发生异常,不然不会继续补偿前面的))
	    Sage.new()
	    |> Sage.run(:orders, &update_order/2, &update_failed/3)
	    |> Sage.run(:notice, &update_notice/2, &update_notice_failed/3)
	    |> Sage.run(:test, &update_test/2, &update_test_failed/3)
	    |> Sage.run()
	    |> ...
	    |> Sage.finally(&acknowledge_job/2)
	    |> saga_transaction(attrs)
	"""
	@spec saga_transaction(struct, pid | atom, any) :: :ok | {:ok, any} | {:ok, {:ok, any}}
	def saga_transaction(sage, conn, args, opts) do
		Postgrex.transaction(
			conn,
			fn (conn) ->
				put(conn)
				reply = case Sage.execute(sage, args) do
					{:ok, result, _effects} -> {:ok, result}
					{:error, reason} -> throw({:error, "failed", reason})
					reason -> throw({:error, "failed", reason})
				end
				erase_conn()
				{:ok, reply}
			end
		)
	end

	def transaction(fun, opts \\ []) when is_function(fun, 0) do
		Postgrex.transaction(
			get_conn(),
			fn (conn) ->
				put(conn)
				try do
					fun.()
				after
					Process.delete(@tran_conn)
				end
			end,
			opts
		)
	end

	def set_iso_level(level) do
		case level do
			:max ->
				PsqlDb.query!("SET TRANSACTION ISOLATION LEVEL SERIALIZABLE")
			_ ->
				:ok
		end
	end

	def read(project_id, mod, select, where, opts \\ []) do
		{str, params} = PsqlLib.query(project_id, mod, select, where, opts)
		rows = query(str, params)
		if opts[:return_json] != nil do
			case rows do
				[[nil]] ->
					[]
				[[row]] ->
					row
			end
		else
			rows
		end
	end

	def count(project_id, mod, where, opts \\ []) do
		{str, params} = PsqlLib.count(project_id, mod, where, opts)
		[[num]] = query(str, params)
		num
	end

	def read!(project_id, mod, select, where, opts \\ []) do
		resp = read(project_id, mod, select, where, opts)
		resp == [] && raise TipError, "input_error"
		resp
	end

	def exists?(project_id, mod, select, where, opts \\ []) do
		{str, params} = PsqlLib.query(project_id, mod, select, where, opts)
		query(str, params) != []
	end


	@doc """
	    opts:
				sql: "returning..."  |
				ON CONFLICT(*) DO ...
				not_exists: string, 不存在则插入
	"""
	@spec insert(charlist, [struct] | struct, keyword) :: list | charlist | nil
	def insert(project_id, args, opts \\ []) do
		{str, vals} = case opts[:not_exists] do
			nil ->
				PsqlLib.insert(project_id, args, opts)
			_ ->
				PsqlLib.insert_not_exists(project_id, args, opts)
		end
		query!(str, vals, opts)
	end

	@doc """
	    opts: sql: "returning..."  | ON CONFLICT(*) DO ...
		update: [{"sql", str}], key为"sql"的时候, 就把str拼接上去,服务器生成的数据才能这样做
				[return_old: [string], return_new: [string]]
				return_old, 更新之后返回旧数据
				return_new 要有 return_old 才能生效
	"""
	def update!(project_id, mod, update, where, opts \\ []) do
		{str, params} = PsqlLib.update(project_id, mod, update, where, opts)
		query!(str, params, opts)
	end

	def update(project_id, mod, update, where, opts \\ []) do
		{str, params} = PsqlLib.update(project_id, mod, update, where, opts)
		query(str, params)
	end

	def delete!(project_id, mod, where, opts \\ []) do
		{str, params} = PsqlLib.delete(project_id, mod, where, opts)
		query!(str, params, opts)
	end

	def delete(project_id, mod, where, opts \\ []) do
		{str, params} = PsqlLib.delete(project_id, mod, where, opts)
		query(str, params)
	end

	#  只执行
	@spec query(charlist, keyword) :: list | charlist
	def query(sql, params \\ []) do
		with {:ok, result} <- run(sql, params) do
			result.rows
		else
			{:error, error} ->
				Logger.error(inspect(error))
				raise TipError, "unexpected_error"
		end
	end

	@spec query(charlist, keyword) :: list | charlist | nil
	def query!(sql, params \\ [], opts \\ []) do
		with {:ok, result} <- run(sql, params) do
			success!(result, params, opts)
			result.rows
		else
			{:error, error} ->
				Logger.error(inspect(error))
				raise TipError, "unexpected_error"
		end
	end

	@spec run(charlist, [any]) :: {:ok, Postgrex.Result.t()} | {:error, Exception.t()}
	def run(sql, params \\ []) do
		AppConfigure.fetch(:show_sql) &&
			Logger.debug("\n#{sql}\n#{inspect(params)}")
		Postgrex.query(get_conn(), sql, params)
	end


	def get_conn() do
		conn = Process.get(@tran_conn)
		with true <- Kernel.is_map(conn),
				 DBConnection <- Map.get(conn, :__struct__),
				 status = DBConnection.status(conn),
				 true <- status != :error do
			conn
		else
			_pid ->
				@psql_pool
		end
	end

	def erase_conn() do
		Neo4jDb.erase()
		Process.delete(@tran_conn)
	end

	def get_result({:ok, result}) do
		get_result(result)
	end
	def get_result(result) do
		result.rows
	end

	@check_methods [@insert, @update, @delete]
	def success!(result, _params, opts) do
		if Map.get(result, :command) in @check_methods && Map.get(result, :num_rows) == 0 do
			case opts[:tip] do
				nil ->
					raise TipError, "failed"
				tip ->
					raise TipError, tip
			end
		end
	end


	def put(con) do
		Process.put(@tran_conn, con)
	end

end
